package com.neuronbit.businessflow.intellij.model;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jgrapht.graph.DirectedMultigraph;

import java.io.Serializable;
import java.util.*;

/**
 * The type Flow.
 */
public class Flow extends JavaMethodTask implements Serializable {
    private final DirectedMultigraph<Node, Edge> graph = new DirectedMultigraph<>(Edge.class);
    private final Map<String, Node> data = new HashMap<>();
    private boolean reenterable = false;

    /**
     * Instantiates a new Flow.
     */
    public Flow() {
        this(null, null);
    }

    /**
     * Instantiates a new Flow.
     *
     * @param id   the id
     * @param name the name
     */
    public Flow(String id, String name) {
        super(id, name, NodeType.FLOW);
    }

    /**
     * Get node.
     *
     * @param id the id
     * @return the node
     */
    public Node get(String id) {
        return data.get(id);
    }

    /**
     * Put node node.
     *
     * @param node the node
     * @return the node
     */
    public Node putNode(Node node) {
        if (null == node || node.getId() == null) {
            return null;
        }
        Node put = data.put(node.getId(), node);
        graph.addVertex(node);
        return put;
    }

    /**
     * Nodes set.
     *
     * @return the set
     */
    public Set<Node> nodes() {
        return graph.vertexSet();
    }

    /**
     * Put node.
     *
     * @param nodes the nodes
     */
    public void putNode(List<? extends Node> nodes) {
        if (null == nodes) {
            return;
        }

        for (Node node : nodes) {
            putNode(node);
        }
    }

    /**
     * Put edge.
     *
     * @param edges the edges
     */
    public void putEdge(List<? extends Edge> edges) {
        if (null == edges) {
            return;
        }

        for (Edge edge : edges) {
            String source = edge.getSource();
            String target = edge.getTarget();
            if (StringUtils.isNoneBlank(source, target)) {
                Node sourceNode = data.get(source);
                Node targetNode = data.get(target);
                if (ObjectUtils.allNotNull(sourceNode, targetNode)) {
                    edge.setSourceNode(sourceNode);
                    edge.setTargetNode(targetNode);
                    graph.addEdge(sourceNode, targetNode, edge);
                }
            }
        }
    }

    /**
     * Outgoing edges set.
     *
     * @param node the node
     * @return the set
     */
    public Set<Edge> outgoingEdges(Node node) {
        return graph.outgoingEdgesOf(node);
    }

    /**
     * Incoming edges set.
     *
     * @param node the node
     * @return the set
     */
    public Set<Edge> incomingEdges(Node node) {
        return graph.incomingEdgesOf(node);
    }

    /**
     * Successors list.
     *
     * @param node the node
     * @return the list
     */
    public List<Node> successors(Node node) {
        Set<Edge> edges = graph.outgoingEdgesOf(node);
        if (edges != null && !edges.isEmpty()) {
            List<Node> result = new ArrayList<>(edges.size());
            for (Edge edge : edges) {
                result.add(edge.getTargetNode());
            }
            return result;
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Successor node.
     *
     * @param node the node
     * @return the node
     */
    public Node successor(Node node) {
        Set<Edge> edges = graph.outgoingEdgesOf(node);
        if (null != edges && !edges.isEmpty()) {
            return edges.stream().findFirst().get().getTargetNode();
        } else {
            return null;
        }
    }

    /**
     * Gets reenterable.
     *
     * @return the reenterable
     */
    public boolean getReenterable() {
        return reenterable;
    }

    /**
     * Is reenterable boolean.
     *
     * @return the boolean
     */
    public boolean isReenterable() {
        return reenterable;
    }

    /**
     * Sets reenterable.
     *
     * @param reenterable the reenterable
     */
    public void setReenterable(boolean reenterable) {
        this.reenterable = reenterable;
    }

    /**
     * Sets reenterable.
     *
     * @param reenterable the reenterable
     */
    public void setReenterable(Boolean reenterable) {
        this.reenterable = reenterable;
    }
}
